<?php 

/**
 * A geocoder adapter for the Yahoo! Maps Geocode API
 * {@link http://developer.yahoo.com/maps/rest/V1/geocode.html}
 * 
 * @category Noginn
 * @package Noginn_Geocoder
 */
class Noginn_Geocoder_Adapter_Yahoo extends Noginn_Geocoder_Adapter_Abstract
{
    /** Base Yahoo! Maps Geocode API URI */
    const SERVICE_URI = 'http://local.yahooapis.com/MapsService/V1/geocode';
    
    /**
     * The Yahoo! application ID
     *
     * @var string
     */
    protected $_appId;
    
    /**
     * The HTTP client to use for API requests
     *
     * @var Zend_Http_Client
     */
    protected $_httpClient;
    
    /**
     * Maps the precision string returned by the Yahoo API to it's 
     * corresponding accuracy value.
     *
     * @var array
     */
    protected $_precisionToAcuracy = array(
        'address' => 8,
        'street' => 7,
        'zip+4' => 6,
        'zip+2' => 5,
        'zip' => 4,
        'city' => 3,
        'state' => 2,
        'country' => 1,
    );
    
    /**
     * Constructor
     *
     * @param array|Zend_Config $config 
     * @return void
     */
    public function __construct($config = array())
    {
        iconv_set_encoding('output_encoding', 'UTF-8');
        iconv_set_encoding('input_encoding', 'UTF-8');
        iconv_set_encoding('internal_encoding', 'UTF-8');

        if (array_key_exists('appId', $config)) {
            $this->setAppId($config['appId']);
        }
    }
    
    /**
     * Set the Yahoo! application ID
     *
     * @param string $appId 
     * @return void
     */
    public function setAppId($appId)
    {
        $this->_appId = $appId;
    }
    
    /**
     * Return the Yahoo! application ID
     *
     * @return string
     */
    public function getAppId()
    {
        return $this->_appId;
    }
    
    /**
     * Return the HTTP client. 
     * If not set then create a new instance of Zend_Http_Client.
     *
     * @return Zend_Http_Client
     */
    public function getHttpClient()
    {
        if (is_null($this->_httpClient)) {
            $this->_httpClient = new Zend_Http_Client(self::SERVICE_URI);
        }

        return $this->_httpClient;
    }
    
    /**
     * Specify the Zend_Http_Client object to use
     *
     * @param Zend_Http_Client $httpClient 
     * @return void
     */
    public function setHttpClient(Zend_Http_Client $httpClient)
    {
        $httpClient->setUri(self::SERVICE_URI);
        $this->_httpClient = $httpClient;
    }
    
    /**
     * Geocode the address
     *
     * @param string|array $address 
     * @return Noginn_Geocoder_LocationList
     */
    public function geocode($address)
    {
        $address = $this->_buildQuery($address);
        
        // Check if the current location list was cached yet
        $cache = Noginn_Geocoder::getCache();
        
        if ($cache !== null) {
            $cacheId = 'Noginn_Geocoder_Yahoo_' . md5($address);

            if (false !== ($locationList = $cache->load($cacheId))) {
                return $locationList;
            }
        }
        
        $httpClient = $this->getHttpClient();
        $httpClient->resetParameters();
        $httpClient->setParameterGet('output', 'xml');
        $httpClient->setParameterGet('appid', $this->getAppId());
        $httpClient->setParameterGet('location', $this->_buildQuery($address));
        $response = $httpClient->request('GET');
        
        // Check for HTTP client request errors
        if ($response->isError()) {
            throw new Zend_Exception('An error occurred sending the geocode request. Status code: ' . $response->getStatus());
        }

        $xml = new SimpleXMLElement($response->getBody());
        
        // Check for API errors
        if ($xml->getName() == 'Error') {
            throw new Zend_Exception('An error response was sent with the message: ' . $xml->Message);
        }
        
        // Parse the result set
        $locationList = new Noginn_Geocoder_LocationList();
        foreach ($xml->Result as $result) {
            $location = new Noginn_Geocoder_Location();
            
            $location->latitude = (float) $result->Latitude;
            $location->longitude = (float) $result->Longitude;
            
            // Map the precision string to the accuracy number
            $precision = (string) $result['precision'];
            if (array_key_exists($precision, $this->_precisionToAcuracy)) {
                $location->accuracy = $this->_precisionToAcuracy[$precision];
            }
            
            $location->street = (string) $result->Address;
            $location->city = (string) $result->City;
            $location->region = (string) $result->State;
            $location->postalCode = (string) $result->Zip;
            $location->country = (string) $result->Country;
            
            // Build the address string from the available parts
            $addressParts = array();
            if (!empty($result->Address)) {
                $addressParts[] = $result->Address;
            }
            if (!empty($result->City)) {
                $addressParts[] = $result->City;
            }
            if (!empty($result->State)) {
                $addressParts[] = $result->State;
            }
            if (!empty($result->Zip)) {
                $addressParts[] = $result->Zip;
            }
            if (!empty($result->Country)) {
                $addressParts[] = $result->Country;
            }
            $location->address = implode(', ', $addressParts);
            
            $locationList->addLocation($location);
        }
        
        // If caching is enabled, cache the current result
        if ($cache !== null) {
            $cache->save($locationList, $cacheId);
        }
        
        return $locationList;
    }
}